package ru.ifmo.rybakov.math;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RegressionAnalyzer {
	
	private static final Logger logger = LoggerFactory.getLogger(RegressionAnalyzer.class);
	
	private double[][] x;
//	private double[] y;
	double[][] a;
	double[] b;
	private int n;
	private int m;
	
	private LinearEquation linEq;
	
	public RegressionAnalyzer(double[][] x, double y[]) throws DimensionException, NotSolvableException {
		if (x.length != y.length) {
			throw new DimensionException();
		}
		this.x = x;
//		this.y = y;
	
		// build matrix based on input
		m = x.length;
		n = x[0].length;
		
		a = new double[n+1][n+1];
		b = new double[n+1];
		
		// building a:
		// first row
		a[0][0] = m; // first column
		for (int j = 0; j < n; j++) {
			a[0][j + 1] = calculateSum(j);
		}
		// the rest of rows
		for (int i = 0; i < n; i++) {
			a[i + 1][0] = calculateSum(i); // first column
			// the rest of columns
			for (int j = 0; j < n; j++) {
				a[i + 1][j + 1] = calculateSum(i, j);
			}
		}
		
		// building b:
		b[0] = 0;
		for (int k = 0; k < m; k++) {
			b[0] += y[k];
		}
		for (int i = 0; i < n; i++) {
			b[i + 1] = 0;
			for (int k = 0; k < m; k++) {
				b[i + 1] += y[k] * x[k][i];
			}
		}
		linEq = new LinearEquation(a, b);
	}
	
	private double calculateSum(int i, int j) {
		double s = 0;
		for (int k = 0; k < m; k++) {
			s += x[k][j] * x[k][i];
		}
		return s;
	}
	
	private double calculateSum(int j) {
		double s = 0;
		for (int k = 0; k < m; k++) {
			s += x[k][j];
		}
		return s;
	}
	
	public double[][] getA() {
		return this.a;
	}
	
	public double[] getB() {
		return this.b;
	}
	
	public void run() {
		logger.debug("Running gauss method on data");
		linEq.solveByTotalPivotSearch();
		logger.debug("Results were successfully calculated");
	}
	
	public double[] getResult() {
		return linEq.getSolution();
	}
	
	/**
	 * Predicts value based on input data
	 * @param x The vector of parameters to predict on
	 * @return Predicted value
	 * @throws DimensionException If input data vector size is invalid
	 */
	public double predict(double[] x) throws DimensionException {
		double[] result = linEq.getSolution();
		if (result == null) { // runs method if not run earlier
			run();
			result = linEq.getSolution();
		}
		if (x.length != (result.length - 1)) {
			throw new DimensionException("Input data dimension is invalid");
		}
		double r = result[0];
		for (int i = 0; i < x.length; i++) {
			r += result[i + 1] * x[i];
		}
		return r;
	}
}
